diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 3afde81fb0..0b81d6f930 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -9,6 +9,13 @@ on: - 'util/doc.config' - 'util/doc.css' - '.github/workflows/api-docs.yml' + pull_request: + paths: + - 'include/openssl/**' + - 'util/doc.go' + - 'util/doc.config' + - 'util/doc.css' + - '.github/workflows/api-docs.yml' permissions: contents: read @@ -20,8 +27,29 @@ concurrency: cancel-in-progress: false jobs: + validate-api-docs: + concurrency: + group: api-docs-validate-${{ github.ref }} + cancel-in-progress: true + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version: ">=1.22" + - name: Generate API documentation + run: go run util/doc.go + publish-api-docs: + if: github.event_name == 'push' + needs: validate-api-docs runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} @@ -31,10 +59,7 @@ jobs: with: go-version: ">=1.22" - name: Generate API documentation - run: | - mkdir -p api-output - cd util - go run doc.go --config doc.config --out ../pages-output + run: go run util/doc.go - name: Upload pages artifact uses: actions/upload-pages-artifact@v5 with: diff --git a/.gitignore b/.gitignore index 1a47f3a0ba..5134491129 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ symbols.txt /compile_commands.json /scratch awslcTestTmpFile* +/pages-output diff --git a/util/doc.config b/util/doc.config index cea4a5ff17..e5cfec6c2e 100644 --- a/util/doc.config +++ b/util/doc.config @@ -1,5 +1,6 @@ { "BaseDirectory": "..", + "OutputDirectory": "../pages-output", "Sections": [{ "Name": "Low-level infrastructure", "Headers": [ diff --git a/util/doc.go b/util/doc.go index da4b290756..511bb7f9ae 100644 --- a/util/doc.go +++ b/util/doc.go @@ -2,10 +2,11 @@ // doc generates HTML files from the comments in header files. // -// doc expects to be given the path to a JSON file via the --config option. -// From that JSON (which is defined by the Config struct) it reads a list of -// header file locations and generates HTML files for each in the current -// directory. +// doc reads a JSON config file (defined by the Config struct) for a list of +// header file locations and generates HTML files. The config file is located +// automatically relative to this source file, or can be overridden with +// --config. Output is written to OutputDirectory from the config, or +// overridden with --out. package main import ( @@ -18,6 +19,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strconv" "strings" "unicode" @@ -28,7 +30,10 @@ type Config struct { // BaseDirectory is a path to which other paths in the file are // relative. BaseDirectory string - Sections []ConfigSection + // OutputDirectory is the path where generated files will be written. + // Resolved relative to the config file's location. + OutputDirectory string + Sections []ConfigSection } type ConfigSection struct { @@ -908,23 +913,27 @@ func copyFile(outPath string, inFilePath string) error { return os.WriteFile(filepath.Join(outPath, filepath.Base(inFilePath)), bytes, 0666) } +// sourceDir returns the directory containing this source file. +func sourceDir() string { + _, file, _, ok := runtime.Caller(0) + if !ok { + fmt.Println("Failed to determine source directory") + os.Exit(1) + } + return filepath.Dir(file) +} + func main() { var ( - configFlag *string = flag.String("config", "doc.config", "Location of config file") - outputDir *string = flag.String("out", ".", "Path to the directory where the output will be written") + configFlag *string = flag.String("config", "", "Location of config file (default: doc.config next to doc.go)") + outputDir *string = flag.String("out", "", "Path to the output directory (overrides config)") config Config ) flag.Parse() if len(*configFlag) == 0 { - fmt.Printf("No config file given by --config\n") - os.Exit(1) - } - - if len(*outputDir) == 0 { - fmt.Printf("No output directory given by --out\n") - os.Exit(1) + *configFlag = filepath.Join(sourceDir(), "doc.config") } configBytes, err := os.ReadFile(*configFlag) @@ -938,6 +947,31 @@ func main() { os.Exit(1) } + // Resolve BaseDirectory relative to the config file's location, + // so the tool can be run from any working directory. + configDir := filepath.Dir(*configFlag) + if !filepath.IsAbs(config.BaseDirectory) { + config.BaseDirectory = filepath.Join(configDir, config.BaseDirectory) + } + + // Use --out if provided, otherwise fall back to config, resolved + // relative to the config file's location. + if len(*outputDir) == 0 { + if len(config.OutputDirectory) == 0 { + fmt.Printf("No output directory given by --out or OutputDirectory in config\n") + os.Exit(1) + } + *outputDir = config.OutputDirectory + if !filepath.IsAbs(*outputDir) { + *outputDir = filepath.Join(configDir, *outputDir) + } + } + + if err := os.MkdirAll(*outputDir, 0755); err != nil { + fmt.Printf("Failed to create output directory: %s\n", err) + os.Exit(1) + } + headerDescriptions, err := generate(*outputDir, &config) if err != nil { fmt.Printf("Failed to generate output: %s\n", err) @@ -949,7 +983,7 @@ func main() { os.Exit(1) } - if err := copyFile(*outputDir, "doc.css"); err != nil { + if err := copyFile(*outputDir, filepath.Join(configDir, "doc.css")); err != nil { fmt.Printf("Failed to copy static file: %s\n", err) os.Exit(1) }