Passing restored packages as artefacts in GitLab Continuous Integration The 2019 Stack...
Output the Arecibo Message
What is the meaning of Triage in Cybersec world?
On the insanity of kings as an argument against Monarchy
Limit the amount of RAM Mathematica may access?
How to deal with fear of taking dependencies
How to make payment on the internet without leaving a money trail?
How to answer pointed "are you quitting" questioning when I don't want them to suspect
What is the best strategy for white in this position?
Are there any other methods to apply to solving simultaneous equations?
Why is the maximum length of openwrt’s root password 8 characters?
Could JWST stay at L2 "forever"?
Falsification in Math vs Science
Why isn't airport relocation done gradually?
What is a mixture ratio of propellant?
Dual Citizen. Exited the US on Italian passport recently
What could be the right powersource for 15 seconds lifespan disposable giant chainsaw?
What function has this graph?
Pristine Bit Checking
Is "plugging out" electronic devices an American expression?
Lethal sonic weapons
What is the use of option -o in the useradd command?
JSON.serialize: is it possible to suppress null values of a map?
I see my dog run
Realistic Alternatives to Dust: What Else Could Feed a Plankton Bloom?
Passing restored packages as artefacts in GitLab Continuous Integration
The 2019 Stack Overflow Developer Survey Results Are InAutoFac, NHibernate & ASP.NET Web API integrationClean way of passing parametersUnit/integration testsMVC-Web API 2 integrationIntegration testing with in-memory databases strategyPassing parameter to singletonPassing model to _layout.cshtmlSimple Unity IntegrationFull integration test for a Console applicationContinuous Shuffler with “linked loop”
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
$begingroup$
Introduction
I'm writing a script in YAML for building ASP.NET Core 2.2 project using GitLab Continuous Integration. In all YAML samples I could find (and there are not many) for building .NET Core based applications using GitLab CI I could see something like this:
before_script:
- 'dotnet restore'
before_script is running a dependency restore before every job. It makes sense because if you use free GitLab runners (like I do) every job is executed on a different machine. There is no possibility to preserve state from previous jobs (with exception on cache and artifacts, but I'll get to that later). What that means is that on the next job, all previously restored packages will be gone, and they need to be restored again. And on the next job once again. And again on every job in the pipeline which needs the packages. I noticed a redundancy there. A redundancy that takes precious time, because a huge project with a lot of third-party packages takes a while for a full restore.
Using job artifacts
I found a way to preserve those packages and then pass them to the next job via GitLab artifacts:
restore:
stage: restore
script:
- 'dotnet restore --packages .nuget/'
artifacts:
paths:
- 'src/**/obj/*'
- '.nuget/'
Let's break it down. With dotnet restore --packages .nuget/ I explicitly specify a custom directory for packages to be restored. Then I specify two paths which GitLab CI will be interested in when creating a job artifacts. dotnet restore creates a few files with metadata about packages inside a obj/ directory, so these will be needed as well. I include them in src/**/obj/*. Finally, I include the .nuget/ directory which after dotnet restore should contain all restored dependencies.
Note: A dependency restore saves the path where the packages will be kept inside <PROJECT_NAME>/obj/project.assets.json file. After that, there is no need for explicitly specifying where the restored packages are e.g. when building the project.
Eventually, in the next job I use previously created job artifacts by specifying a job dependecy. In that way, GitLab CI knows that it should download job artifacts from the dependant job.
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Whole YAML script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- restore
- build
restore:
stage: restore
script:
- 'dotnet restore --packages="$NUGET_PACKAGES_DIRECTORY"'
artifacts:
paths:
- '$SOURCE_CODE_DIRECTORY/**/$OBJECTS_DIRECTORY/*'
- '$NUGET_PACKAGES_DIRECTORY/'
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Feedback
Please tell me what you think, any weaknesses of my approach, code smells, or maybe a better solution. All kind of constructive feedback appreciated.
c# asp.net-core yaml
$endgroup$
add a comment |
$begingroup$
Introduction
I'm writing a script in YAML for building ASP.NET Core 2.2 project using GitLab Continuous Integration. In all YAML samples I could find (and there are not many) for building .NET Core based applications using GitLab CI I could see something like this:
before_script:
- 'dotnet restore'
before_script is running a dependency restore before every job. It makes sense because if you use free GitLab runners (like I do) every job is executed on a different machine. There is no possibility to preserve state from previous jobs (with exception on cache and artifacts, but I'll get to that later). What that means is that on the next job, all previously restored packages will be gone, and they need to be restored again. And on the next job once again. And again on every job in the pipeline which needs the packages. I noticed a redundancy there. A redundancy that takes precious time, because a huge project with a lot of third-party packages takes a while for a full restore.
Using job artifacts
I found a way to preserve those packages and then pass them to the next job via GitLab artifacts:
restore:
stage: restore
script:
- 'dotnet restore --packages .nuget/'
artifacts:
paths:
- 'src/**/obj/*'
- '.nuget/'
Let's break it down. With dotnet restore --packages .nuget/ I explicitly specify a custom directory for packages to be restored. Then I specify two paths which GitLab CI will be interested in when creating a job artifacts. dotnet restore creates a few files with metadata about packages inside a obj/ directory, so these will be needed as well. I include them in src/**/obj/*. Finally, I include the .nuget/ directory which after dotnet restore should contain all restored dependencies.
Note: A dependency restore saves the path where the packages will be kept inside <PROJECT_NAME>/obj/project.assets.json file. After that, there is no need for explicitly specifying where the restored packages are e.g. when building the project.
Eventually, in the next job I use previously created job artifacts by specifying a job dependecy. In that way, GitLab CI knows that it should download job artifacts from the dependant job.
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Whole YAML script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- restore
- build
restore:
stage: restore
script:
- 'dotnet restore --packages="$NUGET_PACKAGES_DIRECTORY"'
artifacts:
paths:
- '$SOURCE_CODE_DIRECTORY/**/$OBJECTS_DIRECTORY/*'
- '$NUGET_PACKAGES_DIRECTORY/'
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Feedback
Please tell me what you think, any weaknesses of my approach, code smells, or maybe a better solution. All kind of constructive feedback appreciated.
c# asp.net-core yaml
$endgroup$
add a comment |
$begingroup$
Introduction
I'm writing a script in YAML for building ASP.NET Core 2.2 project using GitLab Continuous Integration. In all YAML samples I could find (and there are not many) for building .NET Core based applications using GitLab CI I could see something like this:
before_script:
- 'dotnet restore'
before_script is running a dependency restore before every job. It makes sense because if you use free GitLab runners (like I do) every job is executed on a different machine. There is no possibility to preserve state from previous jobs (with exception on cache and artifacts, but I'll get to that later). What that means is that on the next job, all previously restored packages will be gone, and they need to be restored again. And on the next job once again. And again on every job in the pipeline which needs the packages. I noticed a redundancy there. A redundancy that takes precious time, because a huge project with a lot of third-party packages takes a while for a full restore.
Using job artifacts
I found a way to preserve those packages and then pass them to the next job via GitLab artifacts:
restore:
stage: restore
script:
- 'dotnet restore --packages .nuget/'
artifacts:
paths:
- 'src/**/obj/*'
- '.nuget/'
Let's break it down. With dotnet restore --packages .nuget/ I explicitly specify a custom directory for packages to be restored. Then I specify two paths which GitLab CI will be interested in when creating a job artifacts. dotnet restore creates a few files with metadata about packages inside a obj/ directory, so these will be needed as well. I include them in src/**/obj/*. Finally, I include the .nuget/ directory which after dotnet restore should contain all restored dependencies.
Note: A dependency restore saves the path where the packages will be kept inside <PROJECT_NAME>/obj/project.assets.json file. After that, there is no need for explicitly specifying where the restored packages are e.g. when building the project.
Eventually, in the next job I use previously created job artifacts by specifying a job dependecy. In that way, GitLab CI knows that it should download job artifacts from the dependant job.
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Whole YAML script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- restore
- build
restore:
stage: restore
script:
- 'dotnet restore --packages="$NUGET_PACKAGES_DIRECTORY"'
artifacts:
paths:
- '$SOURCE_CODE_DIRECTORY/**/$OBJECTS_DIRECTORY/*'
- '$NUGET_PACKAGES_DIRECTORY/'
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Feedback
Please tell me what you think, any weaknesses of my approach, code smells, or maybe a better solution. All kind of constructive feedback appreciated.
c# asp.net-core yaml
$endgroup$
Introduction
I'm writing a script in YAML for building ASP.NET Core 2.2 project using GitLab Continuous Integration. In all YAML samples I could find (and there are not many) for building .NET Core based applications using GitLab CI I could see something like this:
before_script:
- 'dotnet restore'
before_script is running a dependency restore before every job. It makes sense because if you use free GitLab runners (like I do) every job is executed on a different machine. There is no possibility to preserve state from previous jobs (with exception on cache and artifacts, but I'll get to that later). What that means is that on the next job, all previously restored packages will be gone, and they need to be restored again. And on the next job once again. And again on every job in the pipeline which needs the packages. I noticed a redundancy there. A redundancy that takes precious time, because a huge project with a lot of third-party packages takes a while for a full restore.
Using job artifacts
I found a way to preserve those packages and then pass them to the next job via GitLab artifacts:
restore:
stage: restore
script:
- 'dotnet restore --packages .nuget/'
artifacts:
paths:
- 'src/**/obj/*'
- '.nuget/'
Let's break it down. With dotnet restore --packages .nuget/ I explicitly specify a custom directory for packages to be restored. Then I specify two paths which GitLab CI will be interested in when creating a job artifacts. dotnet restore creates a few files with metadata about packages inside a obj/ directory, so these will be needed as well. I include them in src/**/obj/*. Finally, I include the .nuget/ directory which after dotnet restore should contain all restored dependencies.
Note: A dependency restore saves the path where the packages will be kept inside <PROJECT_NAME>/obj/project.assets.json file. After that, there is no need for explicitly specifying where the restored packages are e.g. when building the project.
Eventually, in the next job I use previously created job artifacts by specifying a job dependecy. In that way, GitLab CI knows that it should download job artifacts from the dependant job.
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Whole YAML script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- restore
- build
restore:
stage: restore
script:
- 'dotnet restore --packages="$NUGET_PACKAGES_DIRECTORY"'
artifacts:
paths:
- '$SOURCE_CODE_DIRECTORY/**/$OBJECTS_DIRECTORY/*'
- '$NUGET_PACKAGES_DIRECTORY/'
build:
stage: build
script:
- 'dotnet build --no-restore'
dependencies:
- restore
Feedback
Please tell me what you think, any weaknesses of my approach, code smells, or maybe a better solution. All kind of constructive feedback appreciated.
c# asp.net-core yaml
c# asp.net-core yaml
asked Feb 23 at 18:25
PrologProlog
217
217
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
I had a wrong concept about GitLab artifacts. After a good read on GitLab docs, especially the section distinguishing artifacts and cache, I deduced that I should use cache instead of artifacts as it was designed precisely for storing restored dependencies. Artifacts are meant for passing build output and binaries.
I also removed the restore stage, placing the dotnet restore command in a global before_script. Cache can fail and in such scenario the script should gracefully fallback to default 'download-from-internet' behaviour. With --no-restore option enabled it would not happen. Thus, I removed that option from dotnet build command. It won't make a noticeable difference with successfully download cache as a dependency restore with already downloaded packages will execute in next-to-no-time.
Finally, I added cache key, which will keep cache bundles separate for branches and stages.
Updated script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- build
cache:
key: '$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG'
paths:
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/project.assets.json'
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- '$NUGET_PACKAGES_DIRECTORY'
before_script:
- 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
stage: build
script:
- 'dotnet build --no-restore'
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214128%2fpassing-restored-packages-as-artefacts-in-gitlab-continuous-integration%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
I had a wrong concept about GitLab artifacts. After a good read on GitLab docs, especially the section distinguishing artifacts and cache, I deduced that I should use cache instead of artifacts as it was designed precisely for storing restored dependencies. Artifacts are meant for passing build output and binaries.
I also removed the restore stage, placing the dotnet restore command in a global before_script. Cache can fail and in such scenario the script should gracefully fallback to default 'download-from-internet' behaviour. With --no-restore option enabled it would not happen. Thus, I removed that option from dotnet build command. It won't make a noticeable difference with successfully download cache as a dependency restore with already downloaded packages will execute in next-to-no-time.
Finally, I added cache key, which will keep cache bundles separate for branches and stages.
Updated script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- build
cache:
key: '$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG'
paths:
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/project.assets.json'
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- '$NUGET_PACKAGES_DIRECTORY'
before_script:
- 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
stage: build
script:
- 'dotnet build --no-restore'
$endgroup$
add a comment |
$begingroup$
I had a wrong concept about GitLab artifacts. After a good read on GitLab docs, especially the section distinguishing artifacts and cache, I deduced that I should use cache instead of artifacts as it was designed precisely for storing restored dependencies. Artifacts are meant for passing build output and binaries.
I also removed the restore stage, placing the dotnet restore command in a global before_script. Cache can fail and in such scenario the script should gracefully fallback to default 'download-from-internet' behaviour. With --no-restore option enabled it would not happen. Thus, I removed that option from dotnet build command. It won't make a noticeable difference with successfully download cache as a dependency restore with already downloaded packages will execute in next-to-no-time.
Finally, I added cache key, which will keep cache bundles separate for branches and stages.
Updated script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- build
cache:
key: '$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG'
paths:
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/project.assets.json'
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- '$NUGET_PACKAGES_DIRECTORY'
before_script:
- 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
stage: build
script:
- 'dotnet build --no-restore'
$endgroup$
add a comment |
$begingroup$
I had a wrong concept about GitLab artifacts. After a good read on GitLab docs, especially the section distinguishing artifacts and cache, I deduced that I should use cache instead of artifacts as it was designed precisely for storing restored dependencies. Artifacts are meant for passing build output and binaries.
I also removed the restore stage, placing the dotnet restore command in a global before_script. Cache can fail and in such scenario the script should gracefully fallback to default 'download-from-internet' behaviour. With --no-restore option enabled it would not happen. Thus, I removed that option from dotnet build command. It won't make a noticeable difference with successfully download cache as a dependency restore with already downloaded packages will execute in next-to-no-time.
Finally, I added cache key, which will keep cache bundles separate for branches and stages.
Updated script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- build
cache:
key: '$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG'
paths:
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/project.assets.json'
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- '$NUGET_PACKAGES_DIRECTORY'
before_script:
- 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
stage: build
script:
- 'dotnet build --no-restore'
$endgroup$
I had a wrong concept about GitLab artifacts. After a good read on GitLab docs, especially the section distinguishing artifacts and cache, I deduced that I should use cache instead of artifacts as it was designed precisely for storing restored dependencies. Artifacts are meant for passing build output and binaries.
I also removed the restore stage, placing the dotnet restore command in a global before_script. Cache can fail and in such scenario the script should gracefully fallback to default 'download-from-internet' behaviour. With --no-restore option enabled it would not happen. Thus, I removed that option from dotnet build command. It won't make a noticeable difference with successfully download cache as a dependency restore with already downloaded packages will execute in next-to-no-time.
Finally, I added cache key, which will keep cache bundles separate for branches and stages.
Updated script:
image: microsoft/dotnet:2.2-sdk
variables:
SOURCE_CODE_DIRECTORY: 'src'
BINARIES_DIRECTORY: 'bin'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
stages:
- build
cache:
key: '$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG'
paths:
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/project.assets.json'
- '$SOURCE_CODE_DIRECTORY/*/$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- '$NUGET_PACKAGES_DIRECTORY'
before_script:
- 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
stage: build
script:
- 'dotnet build --no-restore'
edited 6 hours ago
Sᴀᴍ Onᴇᴌᴀ
10.3k62168
10.3k62168
answered 6 hours ago
PrologProlog
217
217
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214128%2fpassing-restored-packages-as-artefacts-in-gitlab-continuous-integration%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown