Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

- feat(maven): Add maven target to deploy to Maven Central (#258)
- feat(symbol-collector): Add symbol-collector target (#266)
- ref(maven): Support BOM files in `maven` target (#270)
- feat(maven): Support BOM files in `maven` target (#270)
- feat(maven): Optional `android` config (#271)
- feat(maven): Remove Gradle properties after publishing (#276)

## 0.24.4
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1010,15 +1010,29 @@ PGP signs and publishes packages to Maven Central.
| `mavenSettingsPath` | Path to the Maven `settings.xml` file. |
| `mavenRepoId` | ID of the Maven server in the `settings.xml`. |
| `mavenRepoUrl` | URL of the Maven repository. |
| `android` | Structure containing the data available below. |
| `android` | Android configuration, see below. |

The `android` structure contains the following options:
If your project isn't related to Android, you don't need this configuration and
can set the option to `false`. If not, set the following nested elements:

- `distDirRegex`: pattern of distribution directory names.
- `fileReplaceeRegex` :pattern of substring of distribution module names to be replaced to get the Android distribution file.
- `fileReplaceeRegex`: pattern of substring of distribution module names to be replaced to get the Android distribution file.
- `fileReplacerStr`: string to be replaced in the module names to get the Android distribution file.

**Example**
**Example (without Android config)**

```yaml
targets:
- name: maven
gradleCliPath: ./gradlew
mavenCliPath: scripts/mvnw.cmd
mavenSettingsPath: scripts/settings.xml
mavenRepoId: ossrh
mavenRepoUrl: https://oss.sonatype.org/service/local/staging/deploy/maven2/
android: false
```

**Example (with Android config)**

```yaml
targets:
Expand Down
113 changes: 99 additions & 14 deletions src/targets/__tests__/maven.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function removeTargetSecretsFromEnv(): void {
}
}

function getTargetOptions() {
function getFullTargetConfig(): any {
return {
OSSRH_USERNAME: DEFAULT_OPTION_VALUE,
OSSRH_PASSWORD: DEFAULT_OPTION_VALUE,
Expand All @@ -61,23 +61,98 @@ function getTargetOptions() {
};
}

function getRequiredTargetConfig(): any {
return {
OSSRH_USERNAME: DEFAULT_OPTION_VALUE,
OSSRH_PASSWORD: DEFAULT_OPTION_VALUE,
gradleCliPath: DEFAULT_OPTION_VALUE,
mavenCliPath: DEFAULT_OPTION_VALUE,
mavenSettingsPath: DEFAULT_OPTION_VALUE,
mavenRepoId: DEFAULT_OPTION_VALUE,
mavenRepoUrl: DEFAULT_OPTION_VALUE,
android: false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not null or undefined? I'm wary about variable-type fields.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking about what this config field should answer is "I don't want to configure Android".

  • I think of null as "there's no Android config". Why isn't there an Android config? Have you forgotten about it? This, to me, is like not answering a question.
  • I think of undefined as "the Android config hasn't been defined". Why don't you define it then, if it's a config file? Do you want to set an android config or not? This, to me, is like giving a very ambiguous answer to a question.
  • I think of false as "I don't want to set an Android config, but I'm saying it". This, to me, is like saying you don't want to set the configuration (answering with a nonambiguous answer).

Thus, using false seems the most appropriate for me. Do you have a different opinion about any (or all) of them? What do you think? Is it a best practice to use another type? (I might have thought about the field like isAndroid too.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand and mostly agree with the reasoning. But requiring to be explicit can lead to unnecessary frustration or repetition, especially in configurations like these. I'm almost thinking about it might be better to split android and maven targets and make them share a common core to address your concerns here.

};
}

function createMavenTarget(
targetOptions?: Record<string, unknown>
targetConfig?: Record<string, unknown>
): MavenTarget {
const finalOptions = targetOptions ? targetOptions : getTargetOptions();
const mergedOptions = {
const finalConfig = targetConfig ? targetConfig : getRequiredTargetConfig();
const mergedConfig = {
name: 'maven',
...finalOptions,
...finalConfig,
};
return new MavenTarget(mergedOptions, new NoneArtifactProvider());
return new MavenTarget(mergedConfig, new NoneArtifactProvider());
}

describe('Maven target configuration', () => {
beforeEach(() => removeTargetSecretsFromEnv());
beforeEach(() => setTargetSecretsInEnv());

test('with options', () => {
setTargetSecretsInEnv();
const mvnTarget = createMavenTarget(getTargetOptions());
afterEach(() => removeTargetSecretsFromEnv());

test('no env vars and no options', () => {
removeTargetSecretsFromEnv();
expect(createMavenTarget).toThrowErrorMatchingInlineSnapshot(
`"Required value(s) OSSRH_USERNAME not found in configuration files or the environment. See the documentation for more details."`
);
});

test('env vars without options', () => {
expect(() => createMavenTarget({})).toThrowErrorMatchingInlineSnapshot(
`"Required configuration gradleCliPath not found in configuration file. See the documentation for more details."`
);
});

test('no android config', () => {
const config = getRequiredTargetConfig();
delete config.android;
expect(() => createMavenTarget(config)).toThrowErrorMatchingInlineSnapshot(
`"Required Android configuration was not found in the configuration file. See the documentation for more details"`
);
});

test('incorrect one-line android config', () => {
const config = getRequiredTargetConfig();
config.android = 'yes';
expect(() => createMavenTarget(config)).toThrowErrorMatchingInlineSnapshot(
`"Required Android configuration is incorrect. See the documentation for more details."`
);
});

test('correct one-line android config', () => {
const config = getRequiredTargetConfig();
const mvnTarget = createMavenTarget(config);
expect(mvnTarget.mavenConfig.android).toStrictEqual(config.android);
});

test('incorrect object android config, missing prop', () => {
const config = getFullTargetConfig();
delete config.android.distDirRegex;
expect(() => createMavenTarget(config)).toThrowErrorMatchingInlineSnapshot(
`"Required Android configuration is incorrect. See the documentation for more details."`
);
});

test('incorrect object android config, replaced prop', () => {
const config = getFullTargetConfig();
delete config.android.distDirRegex;
config.android.anotherParam = 'unused';
expect(() => createMavenTarget(config)).toThrowErrorMatchingInlineSnapshot(
`"Required Android configuration is incorrect. See the documentation for more details."`
);
});

test('correct object android config, with additional props', () => {
const config = getFullTargetConfig();
config.android.additionalProp = 'not relevant';
const mvnTarget = createMavenTarget(config);
const androidConfig: any = mvnTarget.mavenConfig.android;
expect(config.android).toMatchObject(androidConfig);
expect(androidConfig.additionalProp).not.toBeDefined();
});

test('minimum required options', () => {
const mvnTarget = createMavenTarget(getRequiredTargetConfig());
targetOptions.map(secret =>
expect(mvnTarget.config).toEqual(
expect.objectContaining({
Expand All @@ -87,10 +162,20 @@ describe('Maven target configuration', () => {
);
});

test('without options', () =>
expect(createMavenTarget).toThrowErrorMatchingInlineSnapshot(
`"Required value(s) OSSRH_USERNAME not found in configuration files or the environment. See the documentation for more details."`
));
test('full target options', () => {
setTargetSecretsInEnv();
const mvnTarget = createMavenTarget(getFullTargetConfig());
targetOptions.map(secret =>
expect(mvnTarget.config).toEqual(
expect.objectContaining({
[secret]: DEFAULT_OPTION_VALUE,
})
)
);
expect(typeof mvnTarget.config.android.distDirRegex).toBe('string');
expect(typeof mvnTarget.config.android.fileReplaceeRegex).toBe('string');
expect(typeof mvnTarget.config.android.fileReplacerStr).toBe('string');
});
});

describe('publish', () => {
Expand Down
46 changes: 32 additions & 14 deletions src/targets/maven.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ export const targetOptions = [
type OptionsType = typeof targetOptions[number];

type AndroidFields = {
android: {
distDirRegex: RegExp;
fileReplaceeRegex: RegExp;
fileReplacerStr: string;
};
android:
| false
| {
distDirRegex: RegExp;
fileReplaceeRegex: RegExp;
fileReplacerStr: string;
};
};

type TargetSettingType = SecretsType | OptionsType;
Expand Down Expand Up @@ -125,14 +127,26 @@ export class MavenTarget extends BaseTarget {
}

private getAndroidSettings(): AndroidFields {
if (this.config.android === false) {
return {
android: false,
};
}

if (!this.config.android) {
throw new ConfigurationError(
'Required Android configuration was not found in the configuration file. ' +
'See the documentation for more details'
);
}

if (
!this.config.android.distDirRegex ||
!this.config.android.fileReplaceeRegex ||
!this.config.android.fileReplacerStr
) {
throw new ConfigurationError(
'Required Android configuration not found in configuration file. ' +
'See the documentation for more details.'
'Required Android configuration is incorrect. See the documentation for more details.'
);
}

Expand Down Expand Up @@ -403,14 +417,18 @@ export class MavenTarget extends BaseTarget {
private getTargetFilename(distDir: string): string {
const moduleName = parse(distDir).base;

const isAndroidDistDir = this.mavenConfig.android.distDirRegex.test(
moduleName
);
return isAndroidDistDir
? moduleName.replace(
if (this.mavenConfig.android !== false) {
const isAndroidDistDir = this.mavenConfig.android.distDirRegex.test(
moduleName
);
if (isAndroidDistDir) {
return moduleName.replace(
this.mavenConfig.android.fileReplaceeRegex,
this.mavenConfig.android.fileReplacerStr
)
: `${moduleName}.jar`;
);
}
}
Comment thread
BYK marked this conversation as resolved.

return `${moduleName}.jar`;
}
}