11package  ch .epfl .scala 
22
3- import  java .nio .charset .StandardCharsets 
43import  java .nio .file .Paths 
54import  java .time .Instant 
65
@@ -22,31 +21,35 @@ import sjsonnew.shaded.scalajson.ast.unsafe.JValue
2221import  sjsonnew .support .scalajson .unsafe .{Parser  =>  JsonParser , _ }
2322
2423object  SubmitDependencyGraph  {
25-   val  Submit  =  " githubSubmitDependencyGraph" 
26-   val  usage :  String  =  s """ $Submit {"projects":[], "scalaVersions":[]} """ 
27-   val  brief  =  " Submit the dependency graph to Github Dependency API." 
28-   val  detail  =  " Submit the dependency graph of a set of projects and scala versions to Github Dependency API" 
24+   val  Generate  =  " githubGenerateSnapshot" 
25+   private  val  GenerateUsage  =  s """ $Generate {"projects":[], "scalaVersions":[]} """ 
26+   private  val  GenerateDetail  =  " Generate the dependency graph of a set of projects and scala versions" 
2927
30-   val  SubmitInternal :  String  =  s " ${Submit }Internal " 
31-   val  internalOnly  =  " internal usage only" 
28+   private  val  GenerateInternal  =  s " ${Generate }Internal " 
29+   private  val  InternalOnly  =  " internal usage only" 
30+ 
31+   val  Submit  =  " githubSubmitSnapshot" 
32+   private  val  SubmitDetail  =  " Submit the dependency graph to Github Dependency API." 
33+ 
34+   def  usage (command : String ):  String  =  s """ $command {"projects":[], "scalaVersions":[]} """ 
3235
3336  val  commands :  Seq [Command ] =  Seq (
34-     Command (Submit , (usage, brief), detail)(inputParser)(submit),
35-     Command .command(SubmitInternal , internalOnly, internalOnly)(submitInternal)
37+     Command (Generate , (GenerateUsage , GenerateDetail ), GenerateDetail )(inputParser)(generate),
38+     Command .command(GenerateInternal , InternalOnly , InternalOnly )(generateInternal),
39+     Command .command(Submit , SubmitDetail , SubmitDetail )(submit)
3640  )
3741
3842  private  lazy  val  http :  HttpClient  =  Gigahorse .http(Gigahorse .config)
3943
40-   private  def  inputParser (state : State ):  Parser [SubmitInput ] = 
44+   private  def  inputParser (state : State ):  Parser [DependencySnapshotInput ] = 
4145    Parsers .any.* .map { raw => 
4246      JsonParser 
4347        .parseFromString(raw.mkString)
44-         .flatMap(Converter .fromJson[SubmitInput ])
48+         .flatMap(Converter .fromJson[DependencySnapshotInput ])
4549        .get
4650    }.failOnException
4751
48-   private  def  submit (state : State , input : SubmitInput ):  State  =  {
49-     checkGithubEnv() //  fail fast if the Github CI environment is incomplete
52+   private  def  generate (state : State , input : DependencySnapshotInput ):  State  =  {
5053    val  loadedBuild  =  state.setting(Keys .loadedBuild)
5154    //  all project refs that have a Scala version
5255    val  projectRefs  =  loadedBuild.allProjectRefs
@@ -65,48 +68,58 @@ object SubmitDependencyGraph {
6568    state.log.info(s " Resolving snapshot of  $buildFile" )
6669
6770    val  initState  =  state
68-       .put(githubSubmitInputKey , input)
71+       .put(githubSnapshotInputKey , input)
6972      .put(githubBuildFile, githubapi.FileInfo (buildFile.toString))
7073      .put(githubManifestsKey, Map .empty[String , Manifest ])
7174      .put(githubProjectsKey, projectRefs)
7275
7376    val  storeAllManifests  =  scalaVersions.flatMap { scalaVersion => 
7477      Seq (s " ++ $scalaVersion" , s " Global/ ${githubStoreDependencyManifests.key}  $scalaVersion" )
7578    }
76-     val  commands  =  storeAllManifests :+  SubmitInternal 
79+     val  commands  =  storeAllManifests :+  GenerateInternal 
7780    commands.toList :::  initState
7881  }
7982
80-   private  def  submitInternal (state : State ):  State  =  {
83+   private  def  generateInternal (state : State ):  State  =  {
8184    val  snapshot  =  githubDependencySnapshot(state)
82-     val  snapshotUrl  =  s " ${githubApiUrl()}/repos/ ${githubRepository()}/dependency-graph/snapshots " 
83- 
8485    val  snapshotJson  =  CompactPrinter (Converter .toJsonUnsafe(snapshot))
85- 
8686    val  snapshotJsonFile  =  IO .withTemporaryFile(" dependency-snapshot-" " .json" =  true ) { file => 
8787      IO .write(file, snapshotJson)
8888      state.log.info(s " Dependency snapshot written to  ${file.getAbsolutePath}" )
8989      file
9090    }
91+     setGithubOutputs(" snapshot-json-path" ->  snapshotJsonFile.getAbsolutePath)
92+     state.put(githubSnapshotFileKey, snapshotJsonFile)
93+   }
9194
95+   def  submit (state : State ):  State  =  {
96+     checkGithubEnv() //  fail if the Github CI environment
97+     val  snapshotJsonFile  =  state
98+       .get(githubSnapshotFileKey)
99+       .getOrElse(
100+         throw  new  MessageOnlyException (
101+           " Missing snapshot file. This command must execute after the githubGenerateSnapshot command" 
102+         )
103+       )
104+     val  snapshotUrl  =  s " ${githubApiUrl()}/repos/ ${githubRepository()}/dependency-graph/snapshots " 
105+     val  job  =  githubJob()
92106    val  request  =  Gigahorse 
93107      .url(snapshotUrl)
94-       .post(snapshotJson,  StandardCharsets . UTF_8 )
108+       .post(snapshotJsonFile )
95109      .addHeaders(
96110        " Content-Type" ->  " application/json" 
97111        " Authorization" ->  s " token  ${githubToken()}" 
98112      )
99113
100-     state.log.info(s " Submiting  dependency snapshot of job {snapshot. job}  to  $snapshotUrl" )
114+     state.log.info(s " Submitting  dependency snapshot of job  to  $snapshotUrl" )
101115    val  result  =  for  {
102116      httpResp <-  Try (Await .result(http.processFull(request), Duration .Inf ))
103117      snapshot <-  getSnapshot(httpResp)
104118    } yield  {
105119      state.log.info(s " Submitted successfully as  $snapshotUrl/ ${snapshot.id}" )
106120      setGithubOutputs(
107121        " submission-id" ->  s " ${snapshot.id}" ,
108-         " submission-api-url" ->  s " ${snapshotUrl}/ ${snapshot.id}" ,
109-         " snapshot-json-path" ->  snapshotJsonFile.getAbsolutePath
122+         " submission-api-url" ->  s " ${snapshotUrl}/ ${snapshot.id}" 
110123      )
111124      state
112125    }
@@ -115,11 +128,9 @@ object SubmitDependencyGraph {
115128  }
116129
117130  //  https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter
118-   private  def  setGithubOutputs (outputs : (String , String )* ):  Unit  =  IO .writeLines(
119-     file(githubOutput),
120-     outputs.toSeq.map { case  (name, value) =>  s " ${name}= ${value}"  },
121-     append =  true 
122-   )
131+   private  def  setGithubOutputs (outputs : (String , String )* ):  Unit  = 
132+     for  (output <-  githubOutput())
133+       IO .writeLines(output, outputs.map { case  (name, value) =>  s " ${name}= ${value}"  }, append =  true )
123134
124135  private  def  getSnapshot (httpResp : FullResponse ):  Try [SnapshotResponse ] = 
125136    httpResp.status match  {
@@ -165,32 +176,32 @@ object SubmitDependencyGraph {
165176  }
166177
167178  private  def  checkGithubEnv ():  Unit  =  {
168-     githubWorkspace()
169-     githubWorkflow()
170-     githubJobName()
171-     githubAction()
172-     githubRunId()
173-     githubSha()
174-     githubRef()
175-     githubApiUrl()
176-     githubRepository()
177-     githubToken()
178-   }
179- 
180-   private  def  githubWorkspace ():  String  =  githubCIEnv(" GITHUB_WORKSPACE" 
181-   private  def  githubWorkflow ():  String  =  githubCIEnv(" GITHUB_WORKFLOW" 
182-   private  def  githubJobName ():  String  =  githubCIEnv(" GITHUB_JOB" 
183-   private  def  githubAction ():  String  =  githubCIEnv(" GITHUB_ACTION" 
184-   private  def  githubRunId ():  String  =  githubCIEnv(" GITHUB_RUN_ID" 
185-   private  def  githubSha ():  String  =  githubCIEnv(" GITHUB_SHA" 
186-   private  def  githubRef ():  String  =  githubCIEnv(" GITHUB_REF" 
187-   private  def  githubApiUrl ():  String  =  githubCIEnv(" GITHUB_API_URL" 
188-   private  def  githubRepository ():  String  =  githubCIEnv(" GITHUB_REPOSITORY" 
189-   private  def  githubToken ():  String  =  githubCIEnv(" GITHUB_TOKEN" 
190-   private  def  githubOutput ():  String  =  githubCIEnv(" GITHUB_OUTPUT" 
191- 
192-   private  def  githubCIEnv (name : String ):  String  = 
193-     Properties .envOrNone(name).getOrElse {
179+     def  check (name : String ):  Unit  =  Properties .envOrNone(name).orElse {
194180      throw  new  MessageOnlyException (s " Missing environment variable  $name. This task must run in a Github Action. " )
195181    }
182+     check(" GITHUB_WORKSPACE" 
183+     check(" GITHUB_WORKFLOW" 
184+     check(" GITHUB_JOB" 
185+     check(" GITHUB_ACTION" 
186+     check(" GITHUB_RUN_ID" 
187+     check(" GITHUB_SHA" 
188+     check(" GITHUB_REF" 
189+     check(" GITHUB_API_URL" 
190+     check(" GITHUB_REPOSITORY" 
191+     check(" GITHUB_TOKEN" 
192+     check(" GITHUB_OUTPUT" 
193+   }
194+ 
195+   private  def  githubWorkspace ():  String  =  Properties .envOrElse(" GITHUB_WORKSPACE" " " 
196+   private  def  githubWorkflow ():  String  =  Properties .envOrElse(" GITHUB_WORKFLOW" " " 
197+   private  def  githubJobName ():  String  =  Properties .envOrElse(" GITHUB_JOB" " " 
198+   private  def  githubAction ():  String  =  Properties .envOrElse(" GITHUB_ACTION" " " 
199+   private  def  githubRunId ():  String  =  Properties .envOrElse(" GITHUB_RUN_ID" " " 
200+   private  def  githubSha ():  String  =  Properties .envOrElse(" GITHUB_SHA" " " 
201+   private  def  githubRef ():  String  =  Properties .envOrElse(" GITHUB_REF" " " 
202+ 
203+   private  def  githubApiUrl ():  String  =  Properties .envOrElse(" GITHUB_API_URL" " " 
204+   private  def  githubRepository ():  String  =  Properties .envOrElse(" GITHUB_REPOSITORY" " " 
205+   private  def  githubToken ():  String  =  Properties .envOrElse(" GITHUB_TOKEN" " " 
206+   private  def  githubOutput ():  Option [File ] =  Properties .envOrNone(" GITHUB_OUTPUT" 
196207}
0 commit comments