Skip to content

Commit d8e8dbb

Browse files
committed
Fix docFxZip source detection and add delegated task properties
- Fixed docFxZip to check source in doFirst instead of onlyIf * Handles cases where source is set in another task's doLast (e.g., msbuild.doLast) * Task now properly detects source set dynamically during build execution * Added detailed logging for source detection debugging - Added delegated properties to DocfxDefaultTask * Tasks now support setting extension properties directly (source, title, etc.) * Provides seamless access: docFx.source = '...', docFx.title = '...' * Properties available: source, docsHome, locale, filter, title, companyName, companyUrl, environment, additionalResources - Fixed docFxInfo task to handle missing docfx gracefully * Task no longer fails when docfx command is not found * Logs warning and continues, allowing build scripts to install DocFX - Updated CHANGELOG.md with all changes
1 parent e0691c4 commit d8e8dbb

File tree

5 files changed

+199
-51
lines changed

5 files changed

+199
-51
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@
66
* **Auto-configure dependencies**: Plugin now automatically detects custom Docs tasks and makes docfxZip depend on them
77
* Plugin automatically hooks docfxZip into preparePublish task if it exists
88
* Removes need for build scripts to manually configure dependencies
9+
* **Delegated properties on tasks**: Tasks now support setting extension properties directly (e.g., `docFx.source = '...'`, `docFx.title = '...'`)
10+
* Properties available: `source`, `docsHome`, `locale`, `filter`, `title`, `companyName`, `companyUrl`, `environment`, `additionalResources`
11+
* This provides seamless access to extension properties from build scripts
912

1013
### Fixed
1114
* Fixed DocFX detection to check `~/.dotnet/tools/docfx` as fallback when `docfx` is not in PATH
1215
* Plugin now correctly finds DocFX installed via `dotnet tool install -g docfx` even when `~/.dotnet/tools` is not in PATH
1316
* Updated `isDocfxNativelySupported()` and `isDocfxInPath()` to check `~/.dotnet/tools/docfx` location
17+
* **Fixed docFxZip task timing issue**: Task now checks for `source` in `doFirst` instead of `onlyIf` to handle cases where `source` is set in another task's `doLast` (e.g., `msbuild.doLast`)
18+
* Task will now properly detect source set dynamically during build execution
19+
* Added detailed logging to help diagnose issues with source detection
20+
* **Fixed docFxInfo task**: Task now handles missing `docfx` command gracefully instead of failing the build
21+
* Logs a warning and continues execution when `docfx` is not available
22+
* Allows build scripts to download/install DocFX without blocking the build
1423

1524
### Changed
1625
* **BREAKING**: Extension renamed from `docfx` to `docFxConfig` to match naming convention (capital F)

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ gradlePlugin {
4242
vcsUrl = 'https://github.com/i-net-software/gradle-docfx-plugin'
4343

4444
plugins {
45-
docfxPlugin {
45+
docFxPlugin {
4646
id = 'de.inetsoftware.docfx'
4747
implementationClass = 'de.inetsoftware.docfx.DocFxPlugin'
4848
displayName = 'Gradle DocFX Plugin'

src/main/groovy/de/inetsoftware/docfx/DocFxPlugin.groovy

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -42,56 +42,85 @@ class DocFxPlugin implements Plugin<Project> {
4242
task.description = "Packages the generated DocFX documentation into a zip file"
4343
task.dependsOn(docsTask)
4444

45+
// Use onlyIf to allow task to run even if source is not set yet
46+
// (source might be set in another task's doLast, e.g., msbuild.doLast)
47+
// Actual checks happen in doFirst after dependencies have run
48+
task.onlyIf {
49+
// Always allow the task to run - we'll check conditions in doFirst
50+
// This handles the case where source is set in another task's doLast
51+
return true
52+
}
53+
4554
task.doFirst {
55+
project.logger.quiet("docFxZip: Starting zip task")
56+
57+
// Check source from extension first, then from docFx task (in case it was set on the task)
58+
def source = extension.source
59+
if ((source == null || source.trim().isEmpty()) && docsTask.hasProperty('source')) {
60+
source = docsTask.source
61+
}
62+
63+
project.logger.quiet("docFxZip: checking conditions, extension.source=${extension.source}, docFx.source=${docsTask.hasProperty('source') ? docsTask.source : 'N/A'}, resolved source=${source}")
64+
65+
if (source == null || source.trim().isEmpty()) {
66+
throw new org.gradle.api.tasks.StopExecutionException("docFxZip SKIPPED: source is not set (null or empty) - docFx task may not have source configured. Make sure docFx.source is set before docFxZip runs.")
67+
}
68+
4669
// Determine the output directory from the docfx.json source
47-
if (extension.source != null && !extension.source.isEmpty()) {
48-
def sourceFile = project.file(extension.source)
49-
if (sourceFile.exists()) {
50-
def sourceDir = sourceFile.parentFile
51-
def siteDir = new File(sourceDir, "_site")
52-
53-
if (siteDir.exists()) {
54-
task.from(siteDir) {
55-
into "/"
56-
}
57-
task.destinationDirectory = project.file("${project.buildDir}/distributions")
58-
task.archiveClassifier = "docfx"
59-
60-
// Set archive base name from source file name
61-
def baseName = sourceFile.name
62-
if (baseName.endsWith('.json')) {
63-
baseName = baseName.substring(0, baseName.length() - 5)
64-
}
65-
66-
// Set archiveBaseName (works for both Gradle 8 and 9)
67-
// In Gradle 8, archiveBaseName is a property
68-
// In Gradle 9+, archiveBaseName is a Property<String>
69-
try {
70-
if (task.hasProperty('archiveBaseName')) {
71-
def prop = task.archiveBaseName
72-
if (prop instanceof org.gradle.api.provider.Property) {
73-
// Gradle 9+ - Property API
74-
if (!prop.isPresent() || prop.get() == project.name) {
75-
prop.set(baseName)
76-
}
77-
} else {
78-
// Gradle 8 - direct property
79-
if (prop == null || prop == project.name) {
80-
task.archiveBaseName = baseName
81-
}
82-
}
83-
}
84-
} catch (Exception e) {
85-
project.logger.debug("Could not set archiveBaseName: ${e.message}")
70+
def sourceFile = project.file(source)
71+
project.logger.quiet("docFxZip: checking source file: ${sourceFile.absolutePath}, exists=${sourceFile.exists()}")
72+
73+
if (!sourceFile.exists()) {
74+
throw new org.gradle.api.tasks.StopExecutionException("docFxZip SKIPPED: source file does not exist: ${sourceFile.absolutePath}")
75+
}
76+
77+
def sourceDir = sourceFile.parentFile
78+
def siteDir = new File(sourceDir, "_site")
79+
project.logger.quiet("docFxZip: checking _site directory: ${siteDir.absolutePath}, exists=${siteDir.exists()}")
80+
81+
if (!siteDir.exists()) {
82+
throw new org.gradle.api.tasks.StopExecutionException("docFxZip SKIPPED: _site directory does not exist at ${siteDir.absolutePath} (docFx task may not have run successfully)")
83+
}
84+
85+
project.logger.quiet("docFxZip: ALL CONDITIONS MET - zipping _site directory from ${siteDir.absolutePath}")
86+
task.from(siteDir) {
87+
into "/"
88+
}
89+
task.destinationDirectory = project.file("${project.buildDir}/distributions")
90+
task.archiveClassifier = "docfx"
91+
92+
// Set archive base name from source file name
93+
def baseName = sourceFile.name
94+
if (baseName.endsWith('.json')) {
95+
baseName = baseName.substring(0, baseName.length() - 5)
96+
}
97+
98+
// Set archiveBaseName (works for both Gradle 8 and 9)
99+
// In Gradle 8, archiveBaseName is a property
100+
// In Gradle 9+, archiveBaseName is a Property<String>
101+
try {
102+
if (task.hasProperty('archiveBaseName')) {
103+
def prop = task.archiveBaseName
104+
if (prop instanceof org.gradle.api.provider.Property) {
105+
// Gradle 9+ - Property API
106+
if (!prop.isPresent() || prop.get() == project.name) {
107+
prop.set(baseName)
86108
}
87109
} else {
88-
project.logger.warn("DocFX output directory '_site' not found at ${siteDir}. Zip task may be empty.")
110+
// Gradle 8 - direct property
111+
if (prop == null || prop == project.name) {
112+
task.archiveBaseName = baseName
113+
}
89114
}
90115
}
91-
} else {
92-
project.logger.warn("DocFX source not set. Zip task will be empty.")
116+
} catch (Exception e) {
117+
project.logger.debug("Could not set archiveBaseName: ${e.message}")
93118
}
94119
}
120+
121+
task.doLast {
122+
project.logger.quiet("docFxZip: Completed zip task")
123+
}
95124
}
96125

97126
// Make docs task finalized by zip task
@@ -118,4 +147,3 @@ class DocFxPlugin implements Plugin<Project> {
118147
}
119148
}
120149
}
121-

src/main/groovy/de/inetsoftware/docfx/DocfxDefaultTask.groovy

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,108 @@ abstract class DocfxDefaultTask extends DefaultTask {
1111
@Internal
1212
DocfxExtension extension = new DocfxExtension()
1313

14+
// Delegate properties to extension for seamless access
15+
// This allows setting task.source = '...' which will set extension.source
16+
17+
@Internal
18+
String getSource() {
19+
return extension?.source
20+
}
21+
22+
void setSource(String source) {
23+
if (extension != null) {
24+
extension.source = source
25+
}
26+
}
27+
28+
@Internal
29+
String getDocsHome() {
30+
return extension?.docsHome
31+
}
32+
33+
void setDocsHome(String docsHome) {
34+
if (extension != null) {
35+
extension.docsHome = docsHome
36+
}
37+
}
38+
39+
@Internal
40+
String getLocale() {
41+
return extension?.locale
42+
}
43+
44+
void setLocale(String locale) {
45+
if (extension != null) {
46+
extension.locale = locale
47+
}
48+
}
49+
50+
@Internal
51+
String getFilter() {
52+
return extension?.filter
53+
}
54+
55+
void setFilter(String filter) {
56+
if (extension != null) {
57+
extension.filter = filter
58+
}
59+
}
60+
61+
@Internal
62+
String getTitle() {
63+
return extension?.title
64+
}
65+
66+
void setTitle(String title) {
67+
if (extension != null) {
68+
extension.title = title
69+
}
70+
}
71+
72+
@Internal
73+
String getCompanyName() {
74+
return extension?.companyName
75+
}
76+
77+
void setCompanyName(String companyName) {
78+
if (extension != null) {
79+
extension.companyName = companyName
80+
}
81+
}
82+
83+
@Internal
84+
String getCompanyUrl() {
85+
return extension?.companyUrl
86+
}
87+
88+
void setCompanyUrl(String companyUrl) {
89+
if (extension != null) {
90+
extension.companyUrl = companyUrl
91+
}
92+
}
93+
94+
@Internal
95+
Map<String, String> getEnvironment() {
96+
return extension?.environment
97+
}
98+
99+
void setEnvironment(Map<String, String> environment) {
100+
if (extension != null) {
101+
extension.environment = environment
102+
}
103+
}
104+
105+
@Internal
106+
Closure getAdditionalResources() {
107+
return extension?.additionalResources
108+
}
109+
110+
void setAdditionalResources(Closure additionalResources) {
111+
if (extension != null) {
112+
extension.additionalResources = additionalResources
113+
}
114+
}
115+
14116
protected static void whenHasValue(String value, Consumer<String> consumer) {
15117
if (value != null && !value.isEmpty()) {
16118
consumer.accept(value)

src/main/groovy/de/inetsoftware/docfx/Info.groovy

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,23 @@ class Info extends DocfxDefaultTask {
1919
List<String> args = []
2020
args.add("--version")
2121

22-
execOps.exec { execSpec ->
23-
execSpec.executable = extension.docsExecutable
24-
execSpec.args = args
25-
// Set environment variables from extension
26-
Map<String, String> envVars = extension.environmentVariables
27-
if (!envVars.isEmpty()) {
28-
execSpec.environment(envVars)
22+
try {
23+
execOps.exec { execSpec ->
24+
execSpec.executable = extension.docsExecutable
25+
execSpec.args = args
26+
// Set environment variables from extension
27+
Map<String, String> envVars = extension.environmentVariables
28+
if (!envVars.isEmpty()) {
29+
execSpec.environment(envVars)
30+
}
31+
// Ignore errors - this is just an info task
32+
execSpec.ignoreExitValue = true
2933
}
34+
} catch (Exception e) {
35+
// If docfx is not available, just log a warning and continue
36+
// The build script will handle downloading/installing docfx if needed
37+
project.logger.warn("docFxInfo: Could not execute docfx --version: ${e.message}")
38+
project.logger.info("docFxInfo: This is normal if docfx is not yet installed. The build script will handle installation if needed.")
3039
}
3140
}
3241
}

0 commit comments

Comments
 (0)